package com.dferreira.repository.data_delegators;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import com.dferreira.languagesteach.constants.Constants;
import com.dferreira.languagesteach.utils.DataPaths;
import com.dferreira.repository.RepositoryDatabaseHelper;
import com.dferreira.repository.data_models.Repository;
import com.dferreira.repository.data_models.RepositoryDependency;
import com.dferreira.repository.data_models.RepositoryPackage;
import com.dferreira.repository.data_utils.XMLRepositoryParser;
import com.j256.ormlite.dao.RuntimeExceptionDao;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.Where;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

@SuppressLint("DefaultLocale")
public class RepositoryDataSource extends AsyncTask<Void, Void, String> {

    private static final String TAG = "RepositoryDataSource";
    private final Context context;
    private URL repositoryDescription;

    /* Data models */
    private Repository repository;
    private List<RepositoryPackage> repositoryPackages;
    private List<RepositoryDependency> repositoryDependencies;
    private RepositoryDatabaseHelper repositoryDbHelper;

    private final DataPaths dataPaths;

    /**
     * Constructor
     *
     * @param context this context will be used to open the database
     */
    public RepositoryDataSource(Context context) {
        this.context = context;
        this.dataPaths = new DataPaths(context);
        this.repositoryDbHelper = new RepositoryDatabaseHelper(context);
    }

    /**
     * Set the repository url
     *
     * @return null         -> everything just run right
     * notnull      -> an message with error
     */
    public String prepare() {
        try {
            this.repositoryDescription = new URL(dataPaths.getRepositoryUrl()
                    + File.separator + dataPaths.getRepositoryDescriptionFile());
            return null;
        } catch (MalformedURLException e) {
            return e.getMessage();
        }
    }


    /**
     * Build the memory structure from repository
     *
     * @param inputStream input stream where the data will be parsed
     * @return null     -> everything worked fine
     * notnull -> an error detected
     */
    private String fetchRepository(InputStream inputStream) {
        // Local variables
        SAXParserFactory spf;
        SAXParser sp;
        XMLReader xr;
        XMLRepositoryParser xmlRepositoryParser;

        InputSource inputSource;

        try {
            // parsing from XML file
            spf = SAXParserFactory.newInstance();
            sp = spf.newSAXParser();
            xr = sp.getXMLReader();
            xmlRepositoryParser = new XMLRepositoryParser();
            xr.setContentHandler(xmlRepositoryParser);

            inputSource = new InputSource(inputStream);
            inputSource.setEncoding("UTF-8");
            xr.parse(inputSource);
            inputStream.close();
            this.repository.setVersion(xmlRepositoryParser
                    .getRepositoryVersion());
            this.repositoryPackages = xmlRepositoryParser
                    .getRepositoryPackages();
            this.repositoryDependencies = xmlRepositoryParser
                    .getRepositoryDependencies();
            return null;

        } catch (Exception e) {
            Log.w(TAG, e);
            return "Can't parse the repository";
        }
    }


    /**
     * @return false   -> don't need to go to the server
     * true    -> requires refresh from data
     */
    private boolean determinesIfNeedsFetchData() {
        Repository localRepository = getFirstRepository();

        if (localRepository == null) {
            return true;
        } else {
            return repository.getVersion() > localRepository.getVersion();
        }
    }

    /**
     * Makes the fetch from memory data to database
     * to do that will use pre-set variables
     *
     * @return null     ->  Everything worked fine
     * notnull  ->  There was a problem passing that stuff
     */
    private String saveMemDataOnDatabase() {
        Hashtable<String, Integer> pkgIDs;
        boolean setDatabase;


        Iterator<RepositoryPackage> iRepositoryPackage;
        Iterator<RepositoryDependency> iRepositoryDependency;

        iRepositoryPackage = repositoryPackages.iterator();
        iRepositoryDependency = repositoryDependencies.iterator();

        setDatabase = determinesIfNeedsFetchData();

        if (setDatabase) {
            try {
                // Clean repository tables
                deleteAllRepositoryDependencies();
                deleteAllRepositoryPackages();
                deleteAllRepositories();
                // Fill repository values
                createRepository(repository);
                // Fill package values
                while (iRepositoryPackage.hasNext()) {
                    RepositoryPackage repositoryPackage = iRepositoryPackage
                            .next();
                    repositoryPackage.setRepository(repository);
                    if (repositoryPackage.getUrl() == null) {
                        repositoryPackage.setUrl(repository.getUrl() + File.separator + repositoryPackage.getName()
                                + Constants.PACKAGE_EXTENSION);
                    }

                    createRepositoryPackage(repositoryPackage);
                }
                // Fill dependency values
                while (iRepositoryDependency.hasNext()) {
                    RepositoryDependency repositoryDependency = iRepositoryDependency
                            .next();
                    createRepositoryDependency(repositoryDependency);
                }

            } catch (Exception e) {
                // Error in between database transaction
                Log.e(TAG, TAG, e);
                return "An error occurred saving data on database";
            }
        }
        return null;
    }

    /**
     * Parse the repository description file
     *
     * @param params nothing really worth came from here
     * @return null     ->  Everything worked fine
     * notnull  ->  There was a problem passing that stuff
     */
    @Override
    protected String doInBackground(Void... params) {
        String rFetchRepository;
        InputStream inputStream;
        URLConnection urlConnection;

        if (repositoryDescription == null) {
            return "can't open the the url";
        }

        this.repository = new Repository();
        this.repository.setUrl(dataPaths.getRepositoryUrl());

        try {
            urlConnection = repositoryDescription.openConnection();
            inputStream = urlConnection.getInputStream();
            rFetchRepository = fetchRepository(inputStream);
            if (rFetchRepository != null) {
                return rFetchRepository;
            }
            return saveMemDataOnDatabase();
        } catch (IOException e) {
            Log.w(TAG, e);
            return null;
        }
    }

    /**
     * @return repository entity
     */
    private Repository getRepository() {
        return getFirstRepository();
    }

    /**
     * @param packageName Name from the package
     * @return Information available about the package.
     */
    public RepositoryPackage getPackageInfo(String packageName) {
        RepositoryPackage pkg;
        Repository repository;

        repository = getRepository();
        pkg = getRepositoryPackageByName(packageName);


        if (pkg == null) {
            return null;
        } else {
            if (pkg.getUrl() == null) {
                pkg.setUrl(repository.getUrl() + File.separator + pkg.getName()
                        + Constants.PACKAGE_EXTENSION);
            }
            pkg.setDependencies(getRepositoryDependenciesByPackage(pkg));
        }
        return pkg;
    }

    /**
     * method for insert a repository
     *
     * @param repository
     * @return
     */
    private int createRepository(Repository repository) {
        RuntimeExceptionDao<Repository, Integer> dao = repositoryDbHelper.getRepositoryDao();
        int i = dao.create(repository);
        return i;
    }

    /**
     * method for delete all rows
     */
    private void deleteAllRepositories() {
        RuntimeExceptionDao<Repository, Integer> dao = repositoryDbHelper.getRepositoryDao();
        List<Repository> list = dao.queryForAll();
        dao.delete(list);
    }

    /**
     * Get the repository row saved in the database
     *
     * @return
     */
    private Repository getFirstRepository() {
        RuntimeExceptionDao<Repository, Integer> dao = repositoryDbHelper.getRepositoryDao();
        QueryBuilder<Repository, Integer> builder = dao.queryBuilder();
        builder.limit(1l);
        try {
            return dao.queryForFirst(builder.prepare());
        } catch (java.sql.SQLException e) {
            Log.w(TAG, e);
            return null;
        }
    }

    /**
     * method for insert a package from the repository
     *
     * @param repositoryPackage
     * @return
     */
    private int createRepositoryPackage(RepositoryPackage repositoryPackage) {
        RuntimeExceptionDao<RepositoryPackage, Integer> dao = repositoryDbHelper.getRepositoryPackageDao();
        int i = dao.create(repositoryPackage);
        return i;
    }

    /**
     * method for delete all rows
     */
    private void deleteAllRepositoryPackages() {
        RuntimeExceptionDao<RepositoryPackage, Integer> dao = repositoryDbHelper.getRepositoryPackageDao();
        List<RepositoryPackage> list = dao.queryForAll();
        dao.delete(list);
    }

    /**
     * Gets a repository package with a certain name
     *
     * @param name name from the repository package
     * @return the repository package or null
     */
    private RepositoryPackage getRepositoryPackageByName(String name) {
        try {
            RuntimeExceptionDao<RepositoryPackage, Integer> dao = repositoryDbHelper.getRepositoryPackageDao();
            QueryBuilder<RepositoryPackage, Integer> builder = dao.queryBuilder();
            Where<RepositoryPackage, Integer> where = builder.where();
            where.eq(RepositoryPackage.COLUMN_NAME_NAME, name);
            builder.limit(1l);

            return dao.queryForFirst(builder.prepare());
        } catch (java.sql.SQLException e) {
            Log.w(TAG, e);
            return null;
        }
    }

    /**
     * method for delete all rows
     */
    private void deleteAllRepositoryDependencies() {
        RuntimeExceptionDao<RepositoryDependency, Integer> dao = repositoryDbHelper.getRepositoryDependencyDao();
        List<RepositoryDependency> list = dao.queryForAll();
        dao.delete(list);
    }

    /**
     * @param repositoryDependency
     * @return
     */
    private int createRepositoryDependency(RepositoryDependency repositoryDependency) {
        RuntimeExceptionDao<RepositoryDependency, Integer> dao = repositoryDbHelper.getRepositoryDependencyDao();
        int i = dao.create(repositoryDependency);
        return i;
    }

    /**
     * @param repositoryPackage package from the repository to find the respective dependencies
     * @return list of dependencies from the selected passed package
     */
    private List<RepositoryPackage> getRepositoryDependenciesByPackage(RepositoryPackage repositoryPackage) {
        try {
            RuntimeExceptionDao<RepositoryDependency, Integer> dao = repositoryDbHelper.getRepositoryDependencyDao();
            QueryBuilder<RepositoryDependency, Integer> builder = dao.queryBuilder();
            Where<RepositoryDependency, Integer> where = builder.where();
            where.eq(RepositoryDependency.COLUMN_NAME_FOR_PACKAGE, repositoryPackage);
            builder.limit(1l);

            List<RepositoryDependency> dependencies = dao.query(builder.prepare());
            List<RepositoryPackage> lstRepositoryPackage = new ArrayList<RepositoryPackage>(dependencies.size());
            Iterator<RepositoryDependency> iRepositoryDependency = dependencies.iterator();
            while (iRepositoryDependency.hasNext()) {
                RepositoryDependency repositoryDependency = iRepositoryDependency
                        .next();
                lstRepositoryPackage.add(repositoryDependency.getFromPackage());
            }
            return lstRepositoryPackage;

        } catch (java.sql.SQLException e) {
            Log.w(TAG, e);
            return null;
        }
    }
}
